#ifndef MULTIPLAYER_C_
#define MULTIPLAYER_C_

#include <ncurses.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "Definitions.h"

/*Tetris.c declarations*/
int  prepareGame(bool gLoad, int *dif, chtype*** field, bool*** mask, struct Figure* f, struct Figure* nextF, long *score, long *timer, struct tm *t1);
int createFigure(struct Figure *f, int dif);
long getTime(struct tm t1);
int drawField(chtype** field, int player);
int createField(chtype*** field);
int createMask(bool*** mask);
int addScore(int dif, long *score, chtype** field, bool** mask, struct Figure *f);
int getNextFigure(int dif, struct Figure** f, struct Figure ** nextF);
int drawSecondWindow(int player);
int showScore(long score, int player);
int showTime(unsigned long int time, int player);
int showFigure(struct Figure* f, int player);
int showNextFigure(struct Figure f, int player);
int rotateFigureLeft(struct Figure* f);
int rotateFigureRight(struct Figure* f);
int insertFigure(chtype** field, bool** mask, struct Figure* f);
int checkLines(chtype** field, bool** mask);
int canMove(bool** mask, struct Figure* f, int dx, int dy);
int showFullScreen(char* s);
int freeGame(chtype*** field, bool*** mask, struct Figure **f, struct Figure **nextF);

/*Multiplayer.c declarations */
int createHost();
int acceptClient(int s);
int connectToHost();
int sendRecvAll(int s, int s2, int* netChoice, int* p2netChoice, chtype ** field, 
					struct Figure* f, struct Figure* nextF, long score, chtype ** p2field, 
					struct Figure* p2f, struct Figure* p2nextF, long *p2score);
int prepareNetGame(int *s, int *s2, int* dif, bool host);
int netgame(int dif, bool host);
int netGameSend(int soc, chtype** field, struct Figure* f, struct Figure* nextF, long score);
int netGameRecv(int soc, chtype** field, struct Figure* f, struct Figure* nextF, long *score);
int netFigureSend(int soc, struct Figure* f);
int netFigureRecv(int soc, struct Figure* f);
void netError(bool host, int s, int s2);

/*----------------------------------------------------------------*/

int createHost() {
	int s, len, port; /* sockets*/
	struct sockaddr_in addr; /*sockets addreses*/
	char servPortStr[6];
	
	move(4, 10);
	printw(" Please, enter sever's PORT : ");
	
	echo();
	move(5, 15);
	scanw("%s", servPortStr);
	servPortStr[5] = 0;
	noecho();
	
	move(10, 10);
	printw(" Press ENTER to create server. ");
	while (getch()!=10);
	
	char portStr[6];
	bkgdset((chtype)COLOR_PAIR(FieldColor));
	clear();
	
	s = socket ( AF_INET, SOCK_STREAM, 0 );
	if (s < 0) {
		move(5,5);
		printw("Can't create server.");
		refresh();
		sleep(2);
		return NetFail;
	}
	
	/* option for reuse socket s if it wasn't close properly */
	int reuseAddr = 1;
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
	
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl ( INADDR_ANY );
	sscanf(servPortStr, "%d", &port);
	addr.sin_port = htons ( port );
	
	len = sizeof ( addr );
	if (bind ( s, ( struct sockaddr *) &addr, len ) < 0) {
		move(5,5);
		printw("Error binding.");
		refresh();
		sleep(2);			
		return NetFail;
	}
	
	if (listen(s, 1) < 0) {
		move(5,5);
		printw("Error listening.");
		refresh();
		sleep(2);			
		return NetFail;
	}
	
	return s;
}

int acceptClient(int s) {
	
	int s2, len; /* sockets*/
	struct sockaddr_in info; /*sockets addreses*/

	move(10,5);
	printw("Press ESC to cancel.\n");	
	move(5,5);
	printw("Waiting for a connection...\n");
	refresh();
	len = sizeof(info);
	refresh();
	
	fd_set rfds;
	struct timeval tv;
	int ready;
	int i = 0;
	halfdelay(1);
	while (1) {
		/* Watch socket to see when it has input. */
	    FD_ZERO(&rfds);
	    FD_SET(s, &rfds);
	
	    /* Wait up to tv_sec seconds. */
	    tv.tv_sec = 0;
	    tv.tv_usec = 5000;
	    
	    /* start watching socket with timeout of 0.5s */   
	    ready = select(s+1 , &rfds, NULL, NULL, &tv);
	    if (ready < 0) {
			move(6,5);
			printw("Error in select.");
			close(s);
			refresh();
			sleep(2);			
			return NetFail;
	    } else if (ready > 0) {
		    /* the data is ready to be recieved */
			s2 = accept ( s, ( struct sockaddr * ) &info, &len );
			if ( s2 < 0) {
				move(6,5);
				printw("Error accepting from client.");
				close(s);
				refresh();
				sleep(2);			
				return NetFail;
		    }
		    break;
	    }else {
	    	if (getch() == 27) {
	    		close(s);
	    		return NetFail;
	    	} else {
	    		move(6,5);
	    		i = (i + 5) % 4;
	    		if (i == 0) {
	    			printw("\\");
	    		}
	    		else if (i == 1) {
	    			addch(ACS_HLINE);
	    		}
	    		else if (i == 2) {
	    			printw("/");
	    		}
	    		else if (i == 3) {
	    			printw("|");
	    		}
	    		move(getmaxy(stdscr)-1, getmaxx(stdscr)-1);
	    	}
	    }
    }
	
	move(6,5);
	printw("Connected.\n");
	refresh();
	
	return s2;
}

int connectToHost() {
	int s2, port; /* sockets*/
	struct sockaddr_in info; /*sockets addreses*/
	struct hostent *hp;	/*host address*/
	
	char ipStr[16], portStr[6];
	bkgdset((chtype)COLOR_PAIR(FieldColor));
	clear();
	
	move(3, 10);
	printw(" Please, enter sever's IP addres : ");
	
	move(4, 15);
	echo();
	scanw("%s", ipStr);
	ipStr[15] = 0;
	move(6, 10);
	printw(" Please, enter sever's PORT : ");
	
	move(7, 15);
	scanw("%s", portStr);
	portStr[5] = 0;
	noecho();
	
	move(10, 10);
	printw(" Press ENTER to connect. ");
	while (getch()!=10);
		
	bkgdset((chtype)COLOR_PAIR(FieldColor));
	clear();
	
	s2 = socket (AF_INET, SOCK_STREAM, 0 );
	if ( s2 < 0) {
		move(5,5);
    	printw("Can't connect.\n");
    	refresh();
    	sleep(2);
	    return NetFail;
    }
	
	move(5,5);
    printw("Trying to connect...\n");
    refresh();
	if ( ( hp = gethostbyname ( ipStr ) ) == NULL ) {
		move(6,5);
		printw ("Could not resolve %s.\n", ipStr);
		refresh();
		sleep(2);
		return NetFail;
	}

	info.sin_family = AF_INET;
	sscanf(portStr, "%d", &port);
	info.sin_port = htons ( port );
	info.sin_addr.s_addr = *( unsigned long * ) hp->h_addr;
	if ( connect ( s2, ( struct sockaddr * ) &info, sizeof ( struct sockaddr ) ) < 0) {
		move(6,5);
		printw ( "Connction refused from host\n" );
		refresh();
		sleep(2);
		return NetFail;
	}
	
    move(6,5);
    printw("Connected.\n");
    refresh();
    
    return s2;
}

int sendRecvAll(int s, int s2, int* netChoice, int* p2netChoice, chtype ** field, 
					struct Figure* f, struct Figure* nextF, long score, chtype ** p2field, 
					struct Figure* p2f, struct Figure* p2nextF, long *p2score) {
	/* Sending Choice*/
    if (send(s2, netChoice, sizeof(*netChoice), 0) <= 0){
		return NetFail;
	}
	
	/* Receiving Opponent's Choice*/
    if (recv(s2, p2netChoice, sizeof(*p2netChoice), 0) <= 0){
		return NetFail;
	}
	
	/* End game if player's choice or oppentnt's choice is lost or exit*/
	if (*netChoice == NetLost || *netChoice == NetExit) {
		return NetEnd;
	}
	else if (*p2netChoice == NetLost || *p2netChoice == NetExit) {
		return NetEnd;
	}
	
	/* Sending Game or Figure depending on player's choice*/
	if (*netChoice == NetField){
		if (netGameSend(s2, field, f, nextF, score) == NetFail){
			return NetFail;
		}
	}
	else if (*netChoice == NetFigure){
		if (netFigureSend(s2, f) == NetFail){
			return NetFail;
		}
	}
	
	/* Receiving Opponent's Game or Figure depending on opponent's choice*/
	if (*p2netChoice == NetField){
		if (netGameRecv(s2, p2field, p2f, p2nextF, p2score) == NetFail) {;
			return NetFail;
		}
	}
	else if (*p2netChoice == NetFigure) {
		if (netFigureRecv(s2, p2f) == NetFail) {
			return NetFail;
		}
	}	
}

int prepareNetGame(int *s, int *s2, int* dif, bool host) {
	if (host) { /*host actions*/
	*s = createHost();
	if (*s == NetFail) return NetFail;
	
	*s2 = acceptClient(*s);
	if (*s2 == NetFail) return NetFail;
	
	if (send(*s2, dif, sizeof(*dif), 0) < 0) {
		netError(host, *s, *s2);
		return NetFail;
	}
	} else { /*client actions*/
	*s2 = connectToHost();
	if (*s2 == NetFail) return NetFail;
		if (recv(*s2, dif, sizeof(*dif), 0) <= 0) {
			netError(host, *s, *s2);
			return NetFail;
		}
	}
}

/* must be refactored with game() but have not time to finish it*/
int netgame(int dif, bool host) {
	chtype** field; /*Player's field*/
	chtype** p2field; /*Opponent field*/
	bool** mask; /*Player's field mask*/
	struct Figure *f = malloc(sizeof(struct Figure)), *nextF = malloc(sizeof(struct Figure)); /*Player's figure and next figure*/
	struct Figure *p2f = malloc(sizeof(struct Figure)), *p2nextF = malloc(sizeof(struct Figure)); /*Opponent figure and next figure*/
	int i = 0, j, choice, netChoice = NetField, p2netChoice = NetField; 
	long score = 0, timer = 0, p2score;
	
	int s, s2;
	
	time_t rawtime; /*time*/
	struct tm t1, *t2;
	
	int plusScore; /*scores for full lines */
	
	bkgdset((chtype)COLOR_PAIR(FieldColor));
	clear();
	
	if (prepareNetGame(&s, &s2, &dif, host) == NetFail) return NetFail;
	
	sleep(2);
	
	if (prepareGame(FALSE, &dif, &field, &mask, f, nextF, &score, NULL, &t1) < 0 ) return ExitGame;
	createField(&p2field);
	createFigure(p2f, dif);
	createFigure(p2nextF, dif);
	
	halfdelay(1);
	do{
		int a = sendRecvAll(s, s2, &netChoice, &p2netChoice, field, f, nextF, score, 
				p2field, p2f, p2nextF, &p2score);
		if ( a == NetFail) {
			netError(host, s, s2);
			return NetFail;
		}
		else if (a == NetEnd) {
			break;
		}
		
		/*drawing game screen*/
		bkgdset((chtype)COLOR_PAIR(FieldScreenColor));
		clear();
		drawField(field, 0);
		drawField(p2field, 1);
		showFigure(f, 0);
		showFigure(p2f, 1);
		drawSecondWindow(0);
		drawSecondWindow(1);
		showNextFigure(*nextF, 0);
		showNextFigure(*p2nextF, 1);
		showScore(score, 0);
		showScore(p2score, 1);
		showTime(timer, 0);
		showTime(timer, 1);
		refresh();
		
		/*getting player's entered key */
		choice = getch();
		if (choice == KEY_UP) { /*rotate*/
			rotateFigureRight(f);
			if (!canMove(mask, f, 0, 0)) {
				rotateFigureLeft(f);
			}
		}
		else if (choice == KEY_DOWN) { /*move down*/
			i = 10 - 4*dif;
		}
		else if (choice == KEY_LEFT) { /*move left*/
			if (canMove(mask, f, -1, 0)) {
				--(f->x);
			}
		}
		else if (choice == KEY_RIGHT) { /*move right*/
			if (canMove(mask, f, 1, 0)) {
				++(f->x);
			}
		}
		
		/*for smooth moving*/
		chtype c;
		if (choice != ERR) c = getch();
		if (c != ERR) c = getch();
		
		/*time changes*/
		timer = getTime(t1);
		
		++i;
		if (i >= 10 - 4*dif) { /*figure fall's*/
			if (canMove(mask, f, 0, 1)) { /*figure can move down*/
				++(f->y);
				netChoice = NetFigure;
			} else { /*figure can not move down*/
				netChoice = NetField;
				addScore(dif, &score, field, mask, f);
				
				/*getting new figure*/
				getNextFigure(dif, &f, &nextF);
				
				if (!canMove(mask, f, 0, 0)) { /*GAME OVER*/
					netChoice = NetLost;
				}				
			}
			i = 0;
		}
		/*exit if player pressed ESC*/
		if (choice == 27) {
			netChoice = NetExit;
		}
	} while(1);
	
	/*deleting player's and opponent's fied's, mask and figures. */
	freeGame(&field, &mask, &f, &nextF);
	freeGame(&p2field, NULL, &p2f, &p2nextF);
	
	/*ending game*/
	if (netChoice == NetLost) {
		showFullScreen("Boooo!!! YOU Lost!!!!!");
		move(15, 10);
		printw("Press ESC to exit.");
		move(getmaxy(stdscr)-1, getmaxx(stdscr)-1);
		while (getch()!=27);
	}
	else if (p2netChoice == NetLost) {
		showFullScreen("Yeah!!! YOU Win!!!!!");
		move(15, 10);
		printw("Press ESC to exit.");
		move(getmaxy(stdscr)-1, getmaxx(stdscr)-1);
		while (getch()!=27);
	}
	else if (p2netChoice == NetExit) {
		showFullScreen("Sorry, Player 2 quit...");
		move(15, 10);
		printw("Press ESC to exit.");
		move(getmaxy(stdscr)-1, getmaxx(stdscr)-1);
		while (getch()!=27);
	}
	
	/*closing sockets*/
	close(s2);
	if (host) {
		close(s);
	}
}

/*field, figure and next figure sending*/
int netGameSend(int soc, chtype** field, struct Figure* f, struct Figure* nextF, long score) {
	if (send(soc, &score, sizeof(score), 0) < 0) return NetFail;

	int i, j;
	for(i=0; i<FieldHeight; ++i){
		if (send(soc, field[i], sizeof(chtype)*FieldLength, 0) < 0) return NetFail;
	}
	if (netFigureSend(soc, f) == NetFail) return NetFail;
	if (netFigureSend(soc, nextF) == NetFail) return NetFail;
	return 1;
}

/*field, figure and next figure receiving*/
int netGameRecv(int soc, chtype** field, struct Figure* f, struct Figure* nextF, long *score) {	
	if (recv(soc, score, sizeof(*score), 0) <= 0) return NetFail;
	
	int i, j;
	for(i=0; i<FieldHeight; ++i){
		if (recv(soc, field[i], sizeof(chtype)*FieldLength, 0) < 0) return NetFail;
	}
	if (netFigureRecv(soc, f) == NetFail) return NetFail;
	if (netFigureRecv(soc, nextF) == NetFail) return NetFail;
	return 1;
}

/*figure sending*/
int netFigureSend(int soc, struct Figure* f) {
	int i;
	if (send(soc, &(f->x), sizeof(f->x), 0) < 0) return NetFail;
	if (send(soc, &(f->y), sizeof(f->y), 0) < 0) return NetFail;
	if (send(soc, &(f->style), sizeof(f->style), 0) < 0) return NetFail;
	if (send(soc, &(f->size), sizeof(f->size), 0) < 0) return NetFail;
	if (send(soc, &(f->num), sizeof(f->num), 0) < 0) return NetFail;
	for(i=0; i<f->size; ++i){
		if (send(soc, &(f->p[i].x), sizeof(f->p[i].x), 0) < 0) return NetFail;
		if (send(soc, &(f->p[i].y), sizeof(f->p[i].y), 0) < 0) return NetFail;
	}
}

/*figure receiving*/
int netFigureRecv(int soc, struct Figure* f) {
	int i;
	if (recv(soc, &(f->x), sizeof(f->x), 0) < 0) return NetFail;
	if (recv(soc, &(f->y), sizeof(f->y), 0) < 0) return NetFail;
	if (recv(soc, &(f->style), sizeof(f->style), 0) < 0) return NetFail;
	if (recv(soc, &(f->size), sizeof(f->size), 0) < 0) return NetFail;
	if (recv(soc, &(f->num), sizeof(f->num), 0) < 0) return NetFail;
	free(f->p);
	f->p = malloc(f->size*sizeof(struct Point));
	for(i=0; i<f->size; ++i){
		if (recv(soc, &(f->p[i].x), sizeof(f->p[i].x), 0) < 0) return NetFail;
		if (recv(soc, &(f->p[i].y), sizeof(f->p[i].y), 0) < 0) return NetFail;
	}
}

/*if network error happends*/
void netError(bool host, int s, int s2) {
	showFullScreen("Network Error.");
	refresh();
	sleep(2);
	close(s2);
	if (host) {
		close(s);
	}
}

#endif /*MULTIPLAYER_C_*/
